home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / pine3.07 / c-client / rfc822.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-03-29  |  48.4 KB  |  1,557 lines

  1. /*
  2.  * Program:    RFC-822 routines (originally from SMTP)
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    27 July 1988
  13.  * Last Edited:    26 April 1992/maintenance edit 29 March 1993
  14.  *
  15.  * Sponsorship:    The original version of this work was developed in the
  16.  *        Symbolic Systems Resources Group of the Knowledge Systems
  17.  *        Laboratory at Stanford University in 1987-88, and was funded
  18.  *        by the Biomedical Research Technology Program of the National
  19.  *        Institutes of Health under grant number RR-00785.
  20.  *
  21.  * Original version Copyright 1988 by The Leland Stanford Junior University.
  22.  * Copyright 1992 by the University of Washington.
  23.  *
  24.  *  Permission to use, copy, modify, and distribute this software and its
  25.  * documentation for any purpose and without fee is hereby granted, provided
  26.  * that the above copyright notices appear in all copies and that both the
  27.  * above copyright notices and this permission notice appear in supporting
  28.  * documentation, and that the name of the University of Washington or The
  29.  * Leland Stanford Junior University not be used in advertising or publicity
  30.  * pertaining to distribution of the software without specific, written prior
  31.  * permission.  This software is made available "as is", and
  32.  * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
  33.  * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
  34.  * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  35.  * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
  36.  * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
  37.  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  38.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  39.  * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
  40.  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  41.  *
  42.  */
  43.  
  44.  
  45. #include <ctype.h>
  46. #include <stdio.h>
  47. #include <time.h>
  48. #include "osdep.h"
  49. #include "mail.h"
  50. #include "rfc822.h"
  51. #include "misc.h"
  52.  
  53. /* RFC-822 static data */
  54.  
  55.  
  56. /* Body formats constant strings, must match definitions in mail.h */
  57.  
  58. const char *body_types[] = {    /* defined body type strings */
  59.   "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
  60.   "X-UNKNOWN"
  61. };
  62.  
  63.  
  64. const char *body_encodings[] = {/* defined body encoding strings */
  65.   "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
  66. };
  67.  
  68.  
  69. /* Token delimiting special characters */
  70.  
  71.                 /* parse-host specials */
  72. const char *hspecials = " ()<>@,;:\\\"";
  73.                 /* parse-word specials */
  74. const char *wspecials = " ()<>@,;:\\\"[]";
  75.                 /* full RFC-822 specials */
  76. const char *rspecials =  "()<>@,;:\\\"[].";
  77.                 /* body token specials */
  78. const char *tspecials = " ()<>@,;:\\\"[]./?=";
  79.  
  80. /* RFC822 writing routines */
  81.  
  82.  
  83. /* Write RFC822 header from message structure
  84.  * Accepts: scratch buffer to write into
  85.  *        message envelope
  86.  *        message body
  87.  */
  88.  
  89. void rfc822_header (header,env,body)
  90.     char *header;
  91.     ENVELOPE *env;
  92.     BODY *body;
  93. {
  94.   if (env->remail) {        /* if remailing */
  95.     long i = strlen (env->remail);
  96.                 /* flush extra blank line */
  97.     if (i > 4 && env->remail[i-4] == '\015') env->remail[i-2] = '\0';
  98.     strcpy (header,env->remail);/* start with remail header */
  99.   }
  100.   else *header = '\0';        /* else initialize header to null */
  101.   rfc822_header_line (&header,"Newsgroups",env,env->newsgroups);
  102.   rfc822_header_line (&header,"Date",env,env->date);
  103.   rfc822_address_line (&header,"From",env,env->from);
  104.   rfc822_address_line (&header,"Sender",env,env->sender);
  105.   rfc822_address_line (&header,"Reply-To",env,env->reply_to);
  106.   rfc822_header_line (&header,"Subject",env,env->subject);
  107.   rfc822_address_line (&header,"To",env,env->to);
  108.   rfc822_address_line (&header,"cc",env,env->cc);
  109. /* bcc's are never written...
  110.  * rfc822_address_line (&header,"bcc",env,env->bcc);
  111.  */
  112.   rfc822_header_line (&header,"In-Reply-To",env,env->in_reply_to);
  113.   rfc822_header_line (&header,"Message-ID",env,env->message_id);
  114.   if (body && !env->remail) {    /* not if remail or no body structure */
  115.     strcat (header,"MIME-Version: 1.0\015\012");
  116.     rfc822_write_body_header (&header,body);
  117.   }
  118. }
  119.  
  120. /* Write RFC822 address from header line
  121.  * Accepts: pointer to destination string pointer
  122.  *        pointer to header type
  123.  *        message to interpret
  124.  *        address to interpret
  125.  */
  126.  
  127. void rfc822_address_line (header,type,env,adr)
  128.     char **header;
  129.     char *type;
  130.     ENVELOPE *env;
  131.     ADDRESS *adr;
  132. {
  133.   char *t;
  134.   char tmp[MAILTMPLEN];
  135.   long i,len;
  136.   char *s = (*header += strlen (*header));
  137.   if (adr) {            /* do nothing if no addresses */
  138.     if (env->remail) strcat (s,"ReSent-");
  139.     strcat (s,type);        /* write header name */
  140.     strcat (s,": ");
  141.     s += (len = strlen (s));    /* initial string length */
  142.     do {            /* run down address list */
  143.       *(t = tmp) = '\0';    /* initially empty string */
  144.                 /* simple case? */
  145.       if (!(adr->personal || adr->adl)) rfc822_address (t,adr);
  146.       else {            /* no, must use phrase <route-addr> form */
  147.     if (adr->personal) {    /* have a personal name? */
  148.       rfc822_cat (t,adr->personal,rspecials);
  149.       strcat (t," <");    /* write address delimiter */
  150.     }
  151.     else strcat (t,"<");    /* write address delimiter */
  152.     rfc822_address (t,adr);    /* write address */
  153.     strcat (t,">");        /* closing delimiter */
  154.       }
  155.                 /* move to next recipient */
  156.       if (adr = adr->next) strcat (t,", ");
  157.                 /* if string would overflow */
  158.       if ((len += (i = strlen (t))) > 78) {
  159.     len = 4 + i;        /* continue it on a new line */
  160.     *s++ = '\015'; *s++ = '\012';
  161.     *s++ = ' '; *s++ = ' '; *s++ = ' '; *s++ = ' ';
  162.       }
  163.       while (*t) *s++ = *t++;    /* write this address */
  164.     } while (adr);
  165.                 /* tie off header line */
  166.     *s++ = '\015'; *s++ = '\012'; *s = '\0';
  167.     *header = s;        /* set return value */
  168.   }
  169. }
  170.  
  171. /* Write RFC822 text from header line
  172.  * Accepts: pointer to destination string pointer
  173.  *        pointer to header type
  174.  *        message to interpret
  175.  *        pointer to text
  176.  */
  177.  
  178. void rfc822_header_line (header,type,env,text)
  179.     char **header;
  180.     char *type;
  181.     ENVELOPE *env;
  182.     char *text;
  183. {
  184.   if (text) sprintf ((*header += strlen (*header)),"%s%s: %s\015\012",
  185.              env->remail ? "ReSent-" : "",type,text);
  186. }
  187.  
  188.  
  189. /* Write RFC822 address
  190.  * Accepts: pointer to destination string
  191.  *        address to interpret
  192.  */
  193.  
  194. void rfc822_write_address (dest,adr)
  195.     char *dest;
  196.     ADDRESS *adr;
  197. {
  198.   while (adr) {
  199.                 /* simple case? */
  200.     if (!(adr->personal || adr->adl)) rfc822_address (dest,adr);
  201.     else {            /* no, must use phrase <route-addr> form */
  202.       if (adr->personal) {    /* have a personal name? */
  203.     rfc822_cat (dest,adr->personal,rspecials);
  204.     strcat (dest," <");    /* write address delimiter */
  205.       }
  206.       else strcat (dest,"<");    /* write address delimiter */
  207.       rfc822_address (dest,adr);/* write address */
  208.       strcat (dest,">");    /* closing delimiter */
  209.     }
  210.     adr = adr->next;        /* get next recipient */
  211.     if (adr) strcat (dest,", ");/* delimit if there is one */
  212.   }
  213. }
  214.  
  215. /* Write RFC822 route-address to string
  216.  * Accepts: pointer to destination string
  217.  *        address to interpret
  218.  */
  219.  
  220. void rfc822_address (dest,adr)
  221.     char *dest;
  222.     ADDRESS *adr;
  223. {
  224.   if (adr) {            /* no-op if no address */
  225.     if (adr->adl) {        /* have an A-D-L? */
  226.       strcat (dest,adr->adl);
  227.       strcat (dest,":");
  228.     }
  229.                 /* write mailbox name */
  230.     rfc822_cat (dest,adr->mailbox,wspecials);
  231.     strcat (dest,"@");        /* host delimiter */
  232.     strcat (dest,adr->host);    /* write host name */
  233.   }
  234. }
  235.  
  236.  
  237. /* Concatenate RFC822 string
  238.  * Accepts: pointer to destination string
  239.  *        pointer to string to concatenate
  240.  *        list of special characters
  241.  */
  242.  
  243. void rfc822_cat (dest,src,specials)
  244.     char *dest;
  245.     char *src;
  246.     const char *specials;
  247. {
  248.   char *s;
  249.   if (strpbrk (src,specials)) {    /* any specials present? */
  250.     strcat (dest,"\"");        /* opening quote */
  251.                 /* truly bizarre characters in there? */
  252.     while (s = strpbrk (src,"\\\"")) {
  253.       strncat (dest,src,s-src);    /* yes, output leader */
  254.       strcat (dest,"\\");    /* quoting */
  255.       strncat (dest,s,1);    /* output the bizarre character */
  256.       src = ++s;        /* continue after the bizarre character */
  257.     }
  258.     if (*src) strcat (dest,src);/* output non-bizarre string */
  259.     strcat (dest,"\"");        /* closing quote */
  260.   }
  261.   else strcat (dest,src);    /* otherwise it's the easy case */
  262. }
  263.  
  264. /* Write body content header
  265.  * Accepts: pointer to destination string pointer
  266.  *        pointer to body to interpret
  267.  */
  268.  
  269. void rfc822_write_body_header (dst,body)
  270.     char **dst;
  271.     BODY *body;
  272. {
  273.   char *s;
  274.   PARAMETER *param = body->parameter;
  275.   sprintf (*dst += strlen (*dst),"Content-Type: %s",body_types[body->type]);
  276.   s = body->subtype ? body->subtype : rfc822_default_subtype (body->type);
  277.   sprintf (*dst += strlen (*dst),"/%s",s);
  278.   if (param) do {
  279.     sprintf (*dst += strlen (*dst),"; %s=",param->attribute);
  280.     rfc822_cat (*dst,param->value,tspecials);
  281.   } while (param = param->next);
  282.   else if (body->type == TYPETEXT) strcat (*dst,"; charset=US-ASCII");
  283.   strcpy (*dst += strlen (*dst),"\015\012");
  284.   if (body->encoding)        /* note: encoding 7BIT never output! */
  285.     sprintf (*dst += strlen (*dst),"Content-Transfer-Encoding: %s\015\012",
  286.          body_encodings[body->encoding]);
  287.   if (body->id) sprintf (*dst += strlen (*dst),"Content-ID: %s\015\012",
  288.              body->id);
  289.   if (body->description)
  290.     sprintf (*dst += strlen (*dst),"Content-Description: %s\015\012",
  291.          body->description);
  292.   strcat (*dst,"\015\012");    /* write terminating blank line */
  293. }
  294.  
  295.  
  296. /* Subtype defaulting (a no-no, but regretably necessary...)
  297.     a no-no;
  298.      but regretably necessary...;
  299.  * Accepts: type code
  300.  * Returns: default subtype name
  301.  */
  302.  
  303. char *rfc822_default_subtype (type)
  304.     unsigned short type;
  305. {
  306.   switch (type) {
  307.   case TYPETEXT:        /* default is TEXT/PLAIN */
  308.     return "PLAIN";
  309.   case TYPEMULTIPART:        /* default is MULTIPART/MIXED */
  310.     return "MIXED";
  311.   case TYPEMESSAGE:        /* default is MESSAGE/RFC822 */
  312.     return "RFC822";
  313.   case TYPEAPPLICATION:        /* default is APPLICATION/OCTET-STREAM */
  314.     return "OCTET-STREAM";
  315.   case TYPEAUDIO:        /* default is AUDIO/BASIC */
  316.     return "BASIC";
  317.   default:            /* others have no default subtype */
  318.     return "UNKNOWN";
  319.   }
  320. }
  321.  
  322. /* RFC822 parsing routines */
  323.  
  324.  
  325. /* Parse an RFC822 message
  326.  * Accepts: pointer to return envelope
  327.  *        pointer to return body
  328.  *        pointer to header
  329.  *        header byte count
  330.  *        pointer to body
  331.  *        body byte count
  332.  *        pointer to local host name
  333.  *        pointer to scratch buffer
  334.  */
  335.  
  336. void rfc822_parse_msg (en,bdy,s,i,b,j,host,tmp)
  337.     ENVELOPE **en;
  338.     BODY **bdy;
  339.     char *s;
  340.     unsigned long i;
  341.                     char *b;
  342.     unsigned long j;
  343.     char *host;
  344.     char *tmp;
  345. {
  346.   char c,*t,*d;
  347.   ENVELOPE *env = (*en = mail_newenvelope ());
  348.   BODY *body = (*bdy = mail_newbody ());
  349.   long MIMEp = NIL;        /* flag that MIME semantics are in effect */
  350.   while (i > 0 && *s != '\n') {    /* until end of header */
  351.     t = tmp;            /* initialize buffer pointer */
  352.     c = ' ';            /* and previous character */
  353.     while (c) {            /* collect text until logical end of line */
  354.       switch (*s) {
  355.       case '\n':        /* newline, possible end of logical line */
  356.                 /* tie off unless next line starts with WS */
  357.     if (s[1] != ' ' && s[1] != '\t') *t = c = '\0';
  358.     break;
  359.       case '\015':        /* return, unlikely but just in case */
  360.       case '\t':        /* tab */
  361.       case ' ':            /* here for all forms of whitespace */
  362.                 /* insert whitespace if not already there */
  363.     if (c != ' ') *t++ = c = ' ';
  364.     break;
  365.       default:            /* all other characters */
  366.     *t++ = c = *s;        /* insert the character into the line */
  367.     break;
  368.       }
  369.       if (--i > 0) s++;        /* get next character */
  370.       else *t++ = c = '\0';    /* end of header */
  371.     }
  372.  
  373.     if (d = strchr (tmp,':')) {    /* find header item type */
  374.       *d++ = '\0';        /* tie off header item, point at its data */
  375.       while (*d == ' ') d++;    /* flush whitespace */
  376.       if (t = strchr (tmp,' ')) *t = '\0';
  377.       switch (*ucase (tmp)) {    /* dispatch based on first character */
  378.       case '>':            /* possible >From: */
  379.     if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
  380.     break;
  381.       case 'B':            /* possible bcc: */
  382.     if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
  383.     break;
  384.       case 'C':            /* possible cc: or Content-<mumble>*/
  385.     if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
  386.     else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') &&
  387.          (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') &&
  388.          (tmp[7] == '-') && body &&
  389.          (MIMEp || (search (s-1,i,"\012MIME-Version",(long) 13))))
  390.       rfc822_parse_content_header (body,tmp+8,d);
  391.     break;
  392.       case 'D':            /* possible Date: */
  393.     if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
  394.     break;
  395.       case 'F':            /* possible From: */
  396.     if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
  397.     break;
  398.       case 'I':            /* possible In-Reply-To: */
  399.     if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO"))
  400.       env->in_reply_to = cpystr (d);
  401.     break;
  402.  
  403.       case 'M':            /* possible Message-ID: or MIME-Version: */
  404.     if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID"))
  405.       env->message_id = cpystr (d);
  406.     else if (!strcmp (tmp+1,"IME-VERSION")) {
  407.                 /* tie off at end of phrase */
  408.       if (t = rfc822_parse_phrase (d)) *t = '\0';
  409.                 /* known version? */
  410.       if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
  411.         mm_log ("Warning: message has unknown MIME version",PARSE);
  412.       MIMEp = T;        /* note that we are MIME */
  413.     }
  414.     break;
  415.       case 'N':            /* possible Newsgroups: */
  416.     if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) {
  417.       t = env->newsgroups = (char *) fs_get (1 + strlen (d));
  418.       while (c = *d++) if (c != ' ' && c != '\t') *t++ = c;
  419.       *t++ = '\0';
  420.     }
  421.     break;
  422.       case 'R':            /* possible Reply-To: */
  423.     if (!strcmp (tmp+1,"EPLY-TO"))
  424.       rfc822_parse_adrlist (&env->reply_to,d,host);
  425.     break;
  426.       case 'S':            /* possible Subject: or Sender: */
  427.     if (!env->subject && !strcmp (tmp+1,"UBJECT"))
  428.       env->subject = cpystr (d);
  429.     else if (!strcmp (tmp+1,"ENDER"))
  430.       rfc822_parse_adrlist (&env->sender,d,host);
  431.     break;
  432.       case 'T':            /* possible To: */
  433.     if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
  434.     break;
  435.       default:
  436.     break;
  437.       }
  438.     }
  439.   }
  440.                 /* default Sender: and Reply-To: to From: */
  441.   if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
  442.   if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
  443.                 /* now parse the body */
  444.   rfc822_parse_content (body,b,j,host,tmp);
  445. }
  446.  
  447. /* Parse a message body content
  448.  * Accepts: pointer to body structure
  449.  *        pointer to body
  450.  *        body byte count
  451.  *        pointer to local host name
  452.  *        pointer to scratch buffer
  453.  */
  454.  
  455. void rfc822_parse_content (body,b,i,h,t)
  456.     BODY *body;
  457.     char *b;
  458.     unsigned long i;
  459.     char *h;
  460.     char *t;
  461. {
  462.   char c,*s,*s1,*d;
  463.   unsigned long j;
  464.   PARAMETER *param;
  465.   PART *part = NIL;
  466.   body->size.ibytes = i;    /* note body byte size in all cases */
  467.   body->size.bytes = (body->encoding == ENCBASE64 ||
  468.               body->encoding == ENCBINARY) ? i : strcrlflen (b,i);
  469.   switch (body->type) {        /* see if anything else special to do */
  470.   case TYPEMESSAGE:        /* encapsulated message */
  471.     body->contents.msg.env = NIL;
  472.     body->contents.msg.body = NIL;
  473.     body->contents.msg.text = NIL;
  474.     body->contents.msg.offset = 0;
  475.     if (strcmp (body->subtype,"RFC822")) break;
  476.     if ((body->encoding == ENCBASE64) || (body->encoding == ENCQUOTEDPRINTABLE)
  477.     || (body->encoding == ENCOTHER)) {
  478.       mm_log ("Unparsable encapsulated message contents",PARSE);
  479.       return;
  480.     }
  481.     c = '\n';            /* initial default assumption is newline */
  482.     for (s = b,j = 0; (i > j) && ((c != '\n') || (*s != '\n')); s++,j++)
  483.       if (*s != '\015') c = *s;    /* hunt for blank line */
  484.     if (i > j) {        /* unless no more text */
  485.       s++; j++;            /* body starts after this */
  486.     }
  487.                 /* note body text offset */
  488.     body->contents.msg.offset = j;
  489.                 /* parse the body */
  490.     rfc822_parse_msg (&body->contents.msg.env,&body->contents.msg.body,b,j,
  491.               s,i - j,h,t);
  492.                 /* count number of lines */
  493.     for (s = b,j = 0; i > j; s++,j++) if (*s == '\n') body->size.lines++;
  494.     break;
  495.   case TYPETEXT:        /* text content */
  496.     if (!body->subtype)        /* default subtype */
  497.       body->subtype = cpystr (rfc822_default_subtype (body->type));
  498.     if (!body->parameter) {    /* default parameters */
  499.       body->parameter = mail_newbody_parameter ();
  500.       body->parameter->attribute = cpystr ("charset");
  501.       body->parameter->value = cpystr ("US-ASCII");
  502.     }
  503.                 /* count number of lines */
  504.     for (s = b,j = 0; i > j; s++,j++) if (*s == '\n') body->size.lines++;
  505.     break;
  506.  
  507.   case TYPEMULTIPART:        /* multiple parts */
  508.     if ((body->encoding == ENCBASE64) || (body->encoding == ENCQUOTEDPRINTABLE)
  509.     || (body->encoding == ENCOTHER)) {
  510.       mm_log ("Unparsable multipart contents",PARSE);
  511.       return;
  512.     }
  513.     *t = '\0';            /* find cookie */
  514.     for (param = body->parameter; param && !*t; param = param->next)
  515.       if (!strcmp (param->attribute,"BOUNDARY")) strcpy (t,param->value);
  516.     if (!*t) strcpy (t,"-");    /* yucky default */
  517.     j = strlen (t);        /* length of cookie and header */
  518.                 /* find each body part */
  519.     for (s = b,c = '\012'; (long) i > (long) j; i--,c = *s++)
  520.       if (((c == '\012') || (c == '\015')) && (*s == '-') && (s[1] == '-') &&
  521.       !strncmp (s + 2,t,j)) {
  522.     s1 = s;            /* have cookie, remember end of previous */
  523.     i -= (j + 2);        /* skip until after the cookie */
  524.     switch (*(s += j + 2)) {/* what follows cookie? */
  525.     case '-':        /* at the end if two dashes */
  526.       if ((s[1] == '-') && ((s[2] == '\012') || (s[2] == '\015'))) {
  527.                 /* if have a final part calculate its size */
  528.         if (part) part->body.size.bytes = (s1 - b) - part->offset;
  529.         i = 2;        /* terminate this scan */
  530.       }
  531.       break;
  532.     case '\015':        /* handle CRLF form */
  533.       if (s[1] == '\012') {    /* if following LF */
  534.         s++; i--;        /* swallow the CR */
  535.       }            /* drop into LF code */
  536.     case '\012':        /* new line */
  537.       s++; i--;        /* swallow it */
  538.       if (part) {        /* calculate size of previous */
  539.         part->body.size.bytes = (s1 - b) - part->offset;
  540.                 /* instantiate next */
  541.         part = part->next = mail_newbody_part ();
  542.       }
  543.                 /* otherwise start new list */
  544.       else part = body->contents.part = mail_newbody_part ();
  545.       part->offset = s - b;    /* note offset from main body */
  546.       break;
  547.     default:        /* whatever it was it wasn't valid */
  548.       break;
  549.     }
  550.       }
  551.  
  552.                 /* parse body parts */
  553.     for (part = body->contents.part; part; part = part->next)
  554.                 /* get size of this part */
  555.       if (i = part->body.size.bytes) {
  556.     s = b + part->offset;    /* and pointer to its text */
  557.                 /* until end of header */
  558.     while (i > 0 && (*s != '\012') && (*s != '\015')) {
  559.       s1 = t;        /* initialize buffer pointer */
  560.       c = ' ';        /* and previous character */
  561.       while (c) {        /* collect text until logical end of line */
  562.         switch (*s) {
  563.         case '\012':    /* newline, possible end of logical line */
  564.                 /* tie off unless next line starts with WS */
  565.           if (s[1] != ' ' && s[1] != '\t') *s1 = c = '\0';
  566.           break;
  567.         case '\015':    /* return, unlikely but just in case */
  568.         case '\t':        /* tab */
  569.         case ' ':        /* insert whitespace if not already there */
  570.           if (c != ' ') *s1++ = c = ' ';
  571.           break;
  572.         default:        /* all other characters */
  573.           *s1++ = c = *s;    /* insert the character into the line */
  574.           break;
  575.         }
  576.         if (i-- > 0) s++;    /* get next character */
  577.                 /* end of header */
  578.         else *s1++ = c = '\0';
  579.       }
  580.                 /* find header item type */
  581.       if (d = strchr (t,':')) {
  582.         *d++ = '\0';    /* tie off header item, point at its data */
  583.                 /* flush whitespace */
  584.         while (*d == ' ') d++;
  585.         if (s1 = strchr (ucase (t),' ')) *s1 = '\0';
  586.         if ((t[0] == 'C') && (t[1] == 'O') && (t[2] == 'N') &&
  587.         (t[3] == 'T') && (t[4] == 'E') && (t[5] == 'N') &&
  588.         (t[6] == 'T') && (t[7] == '-'))
  589.           rfc822_parse_content_header (&part->body,t+8,d);
  590.       }
  591.     }
  592.                 /* skip trailing CR */
  593.     if ((i > 0) && (*s =='\015')) {i--; s++;}
  594.                 /* skip trailing LF */
  595.     if ((i > 0) && (*s =='\012')) {i--; s++;}
  596.     part->offset = s - b;    /* calculate new body part offset */
  597.                 /* now parse it */
  598.     rfc822_parse_content (&part->body,s,i,h,t);
  599.       }
  600.     break;
  601.   default:            /* nothing special to do in any other case */
  602.     break;
  603.   }
  604. }
  605.  
  606. /* Parse RFC822 body content header
  607.  * Accepts: body to write to
  608.  *        possible content name
  609.  *        remainder of header
  610.  */
  611.  
  612. void rfc822_parse_content_header (body,name,s)
  613.     BODY *body;
  614.     char *name;
  615.     char *s;
  616. {
  617.   PARAMETER *param = NIL;
  618.   char tmp[MAILTMPLEN];
  619.   char c,*t;
  620.   long i;
  621.   switch (*name) {
  622.   case 'I':            /* possible Content-ID */
  623.     if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
  624.     break;
  625.   case 'D':            /* possible Content-Description */
  626.     if (!(strcmp (name+1,"ESCRIPTION")) || body->description)
  627.       body->description = cpystr (s);
  628.     break;
  629.   case 'T':            /* possible Content-Type/Transfer-Encoding */
  630.     if (!(strcmp (name+1,"YPE") || body->type || body->subtype ||
  631.       body->parameter)) {
  632.                 /* get type word */
  633.       if (!(name = rfc822_parse_word (s,tspecials))) break;
  634.       c = *name;        /* remember delimiter */
  635.       *name = '\0';        /* tie off type */
  636.       ucase (s);        /* search for body type */
  637.       for (i = 0; (i < TYPEOTHER) && strcmp (s,body_types[i]); i++);
  638.       body->type = i;        /* set body type */
  639.       *name = c;        /* restore delimiter */
  640.       rfc822_skipws (&name);    /* skip whitespace */
  641.       if ((*name == '/') &&    /* subtype? */
  642.       (name = rfc822_parse_word ((s = ++name),tspecials))) {
  643.     c = *name;        /* save delimiter */
  644.     *name = '\0';        /* tie off subtype */
  645.     rfc822_skipws (&s);    /* copy subtype */
  646.     body->subtype = ucase (cpystr (s ? s :
  647.                        rfc822_default_subtype (body->type)));
  648.     *name = c;        /* restore delimiter */
  649.     rfc822_skipws (&name);    /* skip whitespace */
  650.       }
  651.                 /* subtype defaulting is a no-no, but... */
  652.       else {
  653.     body->subtype = cpystr (rfc822_default_subtype (body->type));
  654.     if (!name) {        /* did the fool have a subtype delimiter? */
  655.       name = s;        /* barf, restore pointer */
  656.       rfc822_skipws (&name);/* skip leading whitespace */
  657.     }
  658.       }
  659.  
  660.                 /* parameter list? */
  661.       while (name && (*name == ';') &&
  662.          (name = rfc822_parse_word ((s = ++name),tspecials))) {
  663.     c = *name;        /* remember delimiter */
  664.     *name = '\0';        /* tie off attribute name */
  665.     rfc822_skipws (&s);    /* skip leading attribute whitespace */
  666.     if (!*s) *name = c;    /* must have an attribute name */
  667.     else {            /* instantiate a new parameter */
  668.       if (body->parameter) param = param->next = mail_newbody_parameter ();
  669.       else param = body->parameter = mail_newbody_parameter ();
  670.       param->attribute = ucase (cpystr (s));
  671.       *name = c;        /* restore delimiter */
  672.       rfc822_skipws (&name);/* skip whitespace before equal sign */
  673.       if ((*name != '=') ||    /* missing value is a no-no too */
  674.           !(name = rfc822_parse_word ((s = ++name),tspecials)))
  675.         param->value = cpystr ("UNKNOWN");
  676.       else {        /* good, have equals sign */
  677.         c = *name;        /* remember delimiter */
  678.         *name = '\0';    /* tie off value */
  679.         rfc822_skipws (&s);    /* skip leading value whitespace */
  680.         if (*s) param->value = rfc822_cpy (s);
  681.         *name = c;        /* restore delimiter */
  682.         rfc822_skipws (&name);
  683.       }
  684.     }
  685.       }
  686.       if (!name) {        /* must be end of poop */
  687.     if (param && param->attribute)
  688.       sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
  689.     else strcpy (tmp,"Missing parameter");
  690.     mm_log (tmp,PARSE);
  691.       }
  692.       else if (*name) {        /* must be end of poop */
  693.     sprintf (tmp,"Junk at end of parameters: %.80s",name);
  694.     mm_log (tmp,PARSE);
  695.       }
  696.     }
  697.     else if (!strcmp (name+1,"RANSFER-ENCODING")) {
  698.                 /* flush out any confusing whitespace */
  699.       if (t = strchr (ucase (s),' ')) *t = '\0';
  700.                 /* search for body encoding */
  701.       for (i = 0; (i < ENCOTHER) && strcmp (s,body_encodings[i]); i++);
  702.       body->encoding = i;        /* set body type */
  703.     }
  704.     break;
  705.   default:            /* otherwise unknown */
  706.     break;
  707.   }
  708. }
  709.  
  710.  
  711.  
  712. /* Parse RFC822 address list
  713.  * Accepts: address list to write to
  714.  *        input string
  715.  *        default host name
  716.  */
  717.  
  718. void rfc822_parse_adrlist (lst,string,host)
  719.     ADDRESS **lst;
  720.     char *string;
  721.     char *host;
  722. {
  723.   char tmp[MAILTMPLEN];
  724.   char *p;
  725.   long n = 0;
  726.   ADDRESS *last = *lst;
  727.   ADDRESS *adr;
  728.                 /* run to tail of list */
  729.   if (last) while (last->next) last = last->next;
  730.                 /* loop until string exhausted */
  731.   if (*string != '\0') while (p = string) {
  732.                 /* see if start of group */
  733.     while ((*p == ':' || ((p = rfc822_parse_phrase (string)) && *p == ':')) &&
  734.        ++n) string = ++p;
  735.     rfc822_skipws (&string);    /* skip any following whitespace */
  736.     if (!string) return;    /* punt if unterminated group */
  737.                 /* if not empty group */
  738.     if (*string != ';' || n <= 0) {
  739.                 /* got an address? */
  740.       if (adr = rfc822_parse_address (&string,host)) {
  741.     if (!*lst) *lst = adr;    /* yes, first time through? */
  742.     else last->next = adr;    /* no, append to the list */
  743.     last = adr;        /* set for subsequent linking */
  744.       }
  745.       else if (string) {    /* bad mailbox */
  746.     sprintf (tmp,"Bad mailbox: %.80s",string);
  747.     mm_log (tmp,PARSE);
  748.     return;
  749.       }
  750.     }
  751.                 /* handle end of group */
  752.     if (string && *string == ';' && (--n) >= 0) {
  753.       string++;            /* skip past the semicolon */
  754.       rfc822_skipws (&string);    /* skip any following whitespace */
  755.       switch (*string) {    /* see what follows */
  756.       case ',':            /* another address? */
  757.     ++string;        /* yes, skip past the comma */
  758.     break;
  759.       case '\0':        /* end of string */
  760.     return;
  761.       default:
  762.     sprintf (tmp,"Junk at end of group: %.80s",string);
  763.     mm_log (tmp,PARSE);
  764.     return;
  765.       }
  766.     }
  767.   }
  768. }
  769.  
  770. /* Parse RFC822 address
  771.  * Accepts: pointer to string pointer
  772.  *        default host
  773.  * Returns: address
  774.  *
  775.  * Updates string pointer
  776.  */
  777.  
  778. ADDRESS *rfc822_parse_address (string,defaulthost)
  779.     char **string;
  780.     char *defaulthost;
  781. {
  782.   char tmp[MAILTMPLEN];
  783.   ADDRESS *adr;
  784.   char c,*s;
  785.   char *phrase;
  786.   if (!string) return NIL;
  787.   rfc822_skipws (string);    /* flush leading whitespace */
  788.  
  789.   /* This is much more complicated than it should be because users like
  790.    * to write local addrspecs without "@localhost".  This makes it very
  791.    * difficult to tell a phrase from an addrspec!
  792.    * The other problem we must cope with is a route-addr without a leading
  793.    * phrase.  Yuck!
  794.    */
  795.  
  796.   if (*(s = *string) == '<')     /* note start, handle case of phraseless RA */
  797.     adr = rfc822_parse_routeaddr (s,string,defaulthost);
  798.   else {            /* get phrase if any */
  799.     if ((phrase = rfc822_parse_phrase (s)) &&
  800.     (adr = rfc822_parse_routeaddr (phrase,string,defaulthost))) {
  801.       *phrase = '\0';        /* tie off phrase */
  802.                 /* phrase is a personal name */
  803.       adr->personal = rfc822_cpy (s);
  804.     }
  805.     else adr = rfc822_parse_addrspec (s,string,defaulthost);
  806.   }
  807.                 /* analyze what follows */
  808.   if (*string) switch (c = **string) {
  809.   case ',':            /* comma? */
  810.     ++*string;            /* then another address follows */
  811.     break;
  812.   case ';':            /* possible end of group? */
  813.     break;            /* let upper level deal with it */
  814.   default:
  815.     s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
  816.       "Junk at end of address: %.80s";
  817.     sprintf (tmp,s,*string);
  818.     mm_log (tmp,PARSE);
  819.                 /* falls through */
  820.   case '\0':            /* null-specified address? */
  821.     *string = NIL;        /* punt remainder of parse */
  822.     break;
  823.   }
  824.   return adr;            /* return the address */
  825. }
  826.  
  827. /* Parse RFC822 route-address
  828.  * Accepts: string pointer
  829.  *        pointer to string pointer to update
  830.  * Returns: address
  831.  *
  832.  * Updates string pointer
  833.  */
  834.  
  835. ADDRESS *rfc822_parse_routeaddr (string,ret,defaulthost)
  836.     char *string;
  837.     char **ret;
  838.     char *defaulthost;
  839. {
  840.   char tmp[MAILTMPLEN];
  841.   ADDRESS *adr;
  842.   char *adl = NIL;
  843.   char *routeend = NIL;
  844.   if (!string) return NIL;
  845.   rfc822_skipws (&string);    /* flush leading whitespace */
  846.                 /* must start with open broket */
  847.   if (*string != '<') return NIL;
  848.   if (string[1] == '@') {    /* have an A-D-L? */
  849.     adl = ++string;        /* yes, remember that fact */
  850.     while (*string != ':') {    /* search for end of A-D-L */
  851.                 /* punt if never found */
  852.       if (*string == '\0') return NIL;
  853.       ++string;            /* try next character */
  854.     }
  855.     *string = '\0';        /* tie off A-D-L */
  856.     routeend = string;        /* remember in case need to put back */
  857.   }
  858.                 /* parse address spec */
  859.   if (!(adr = rfc822_parse_addrspec (++string,ret,defaulthost))) {
  860.     if (adl) *routeend = ':';    /* put colon back since parse barfed */
  861.     return NIL;
  862.   }
  863.                 /* have an A-D-L? */
  864.   if (adl) adr->adl = cpystr (adl);
  865.                 /* make sure terminated OK */
  866.     if (*ret) if (**ret == '>') {
  867.     ++*ret;            /* skip past the broket */
  868.     rfc822_skipws (ret);    /* flush trailing WS */
  869.                 /* wipe pointer if at end of string */
  870.     if (**ret == '\0') *ret = NIL;
  871.     return adr;            /* return the address */
  872.   }
  873.   sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox,adr->host);
  874.   mm_log (tmp,PARSE);
  875.   return adr;            /* return the address */
  876. }
  877.  
  878. /* Parse RFC822 address-spec
  879.  * Accepts: string pointer
  880.  *        pointer to string pointer to update
  881.  *        default host
  882.  * Returns: address
  883.  *
  884.  * Updates string pointer
  885.  */
  886.  
  887. ADDRESS *rfc822_parse_addrspec (string,ret,defaulthost)
  888.     char *string;
  889.     char **ret;
  890.     char *defaulthost;
  891. {
  892.   ADDRESS *adr;
  893.   char *end;
  894.   char c,*s,*t;
  895.   if (!string) return NIL;
  896.   rfc822_skipws (&string);    /* flush leading whitespace */
  897.                 /* find end of mailbox */
  898.   if (!(end = rfc822_parse_word (string,NIL))) return NIL;
  899.   adr = mail_newaddr ();    /* create address block */
  900.   c = *end;            /* remember delimiter */
  901.   *end = '\0';            /* tie off mailbox */
  902.                 /* copy mailbox */
  903.   adr->mailbox = rfc822_cpy (string);
  904.   *end = c;            /* restore delimiter */
  905.   rfc822_skipws (&end);        /* skip whitespace */
  906.   if (*end == '@') {        /* have host name? */
  907.     ++end;            /* skip delimiter */
  908.     rfc822_skipws (&end);    /* skip whitespace */
  909.     *ret = end;            /* update return pointer */
  910.  
  911.                     /* search for end of host */
  912.  
  913.     if (end = rfc822_parse_word ((string = end),hspecials)) {
  914.       c = *end;            /* remember delimiter */
  915.       *end = '\0';        /* tie off host */
  916.                 /* copy host */
  917.       adr->host = rfc822_cpy (string);
  918.       *end = c;            /* restore delimiter */
  919.       if (!adr->personal) {    /* see if we can find a person name here */
  920.     while (*end == ' ') ++end;
  921.                 /* found something that may be a name? */
  922.     if ((*end == '(') && (t = s = rfc822_parse_phrase (end + 1))) {
  923.       rfc822_skipws (&s);    /* heinous kludge for trailing WS in comment */
  924.       if (*s == ')') {    /* look like good end of comment? */
  925.         *t = '\0';        /* yes, tie off the likely name and copy */
  926.         ++end;        /* skip over open paren now */
  927.         rfc822_skipws (&end);
  928.         adr->personal = rfc822_cpy (end);
  929.         end = ++s;        /* skip after it */
  930.       }
  931.     }
  932.     rfc822_skipws (&end);    /* skip any other WS in the normal way */
  933.       }
  934.       *ret = end;        /* set new end pointer */
  935.     }
  936.                 /* bogus input, shove in default */
  937.     else adr->host = cpystr (defaulthost);
  938.   }
  939.   else {            /* no host name */
  940.     *ret = end;            /* update return pointer */
  941.                 /* and default host to current host */
  942.     adr->host = cpystr (defaulthost);
  943.   }
  944.   if (**ret == '\0') *ret = NIL;/* wipe pointer if at end of string */
  945.   return adr;            /* return the address we got */
  946. }
  947.  
  948. /* Parse RFC822 phrase
  949.  * Accepts: string pointer
  950.  * Returns: pointer to end of phrase
  951.  */
  952.  
  953. char *rfc822_parse_phrase (s)
  954.     char *s;
  955. {
  956.   char *curpos;
  957.   if (!s) return NIL;        /* no-op if no string */
  958.                 /* find first word of phrase */
  959.   curpos = rfc822_parse_word (s,NIL);
  960.   if (!curpos) return NIL;    /* no words means no phrase */
  961.                 /* check if string ends with word */
  962.   if (*curpos == '\0') return curpos;
  963.   s = curpos;            /* sniff past the end of this word and WS */
  964.   rfc822_skipws (&s);        /* skip whitespace */
  965.                 /* recurse to see if any more */
  966.   return (s = rfc822_parse_phrase (s)) ? s : curpos;
  967. }
  968.  
  969. /* Parse RFC822 word
  970.  * Accepts: string pointer
  971.  * Returns: pointer to end of word
  972.  */
  973.  
  974. char *rfc822_parse_word (s,delimiters)
  975.     char *s;
  976.     const char *delimiters;
  977. {
  978.   char *st,*str;
  979.   if (!s) return NIL;        /* no-op if no string */
  980.   rfc822_skipws (&s);        /* flush leading whitespace */
  981.   if (*s == '\0') return NIL;    /* end of string */
  982.                 /* default delimiters to standard */
  983.   if (!delimiters) delimiters = wspecials;
  984.   str = s;            /* hunt pointer for strpbrk */
  985.   while (T) {            /* look for delimiter */
  986.     if (!(st = strpbrk (str,delimiters))) {
  987.       while (*s != '\0') ++s;    /* no delimiter, hunt for end */
  988.       return s;            /* return it */
  989.     }
  990.     switch (*st) {        /* dispatch based on delimiter */
  991.     case '"':            /* quoted string */
  992.                 /* look for close quote */
  993.       while (*++st != '"') switch (*st) {
  994.       case '\0':        /* unbalanced quoted string */
  995.     return NIL;        /* sick sick sick */
  996.       case '\\':        /* quoted character */
  997.     ++st;            /* skip the next character */
  998.       default:            /* ordinary character */
  999.     break;            /* no special action */
  1000.       }
  1001.       str = ++st;        /* continue parse */
  1002.       break;
  1003.     case '\\':            /* quoted character */
  1004.       str = st + 2;        /* skip quoted character and go on */
  1005.       break;
  1006.     default:            /* found a word delimiter */
  1007.       return (st == s) ? NIL : st;
  1008.     }
  1009.   }
  1010. }
  1011.  
  1012. /* Copy an RFC822 format string
  1013.  * Accepts: string
  1014.  * Returns: copy of string
  1015.  */
  1016.  
  1017. char *rfc822_cpy (src)
  1018.     char *src;
  1019. {
  1020.                 /* copy and unquote */
  1021.   return rfc822_quote (cpystr (src));
  1022. }
  1023.  
  1024.  
  1025. /* Unquote an RFC822 format string
  1026.  * Accepts: string
  1027.  * Returns: string
  1028.  */
  1029.  
  1030. char *rfc822_quote (src)
  1031.     char *src;
  1032. {
  1033.   char *ret = src;
  1034.   if (strpbrk (src,"\\\"")) {    /* any quoting in string? */
  1035.     char *dst = ret;
  1036.     while (*src) {        /* copy string */
  1037.       if (*src == '\"') src++;    /* skip double quote entirely */
  1038.       else {
  1039.     if (*src == '\\') src++;/* skip over single quote, copy next always */
  1040.     *dst++ = *src++;    /* copy character */
  1041.       }
  1042.     }
  1043.     *dst = '\0';        /* tie off string */
  1044.   }
  1045.   return ret;            /* return our string */
  1046. }
  1047.  
  1048.  
  1049. /* Copy address list
  1050.  * Accepts: address list
  1051.  * Returns: address list
  1052.  */
  1053.  
  1054. ADDRESS *rfc822_cpy_adr (adr)
  1055.     ADDRESS *adr;
  1056. {
  1057.   ADDRESS *dadr;
  1058.   ADDRESS *ret = NIL;
  1059.   ADDRESS *prev = NIL;
  1060.   while (adr) {            /* loop while there's still an MAP adr */
  1061.     dadr = mail_newaddr ();    /* instantiate a new address */
  1062.     if (!ret) ret = dadr;    /* note return */
  1063.     if (prev) prev->next = dadr;/* tie on to the end of any previous */
  1064.     dadr->personal = cpystr (adr->personal);
  1065.     dadr->adl = cpystr (adr->adl);
  1066.     dadr->mailbox = cpystr (adr->mailbox);
  1067.     dadr->host = cpystr (adr->host);
  1068.     prev = dadr;        /* this is now the previous */
  1069.     adr = adr->next;        /* go to next address in list */
  1070.   }
  1071.   return (ret);            /* return the MTP address list */
  1072. }
  1073.  
  1074. /* Skips RFC822 whitespace
  1075.  * Accepts: pointer to string pointer
  1076.  */
  1077.  
  1078. void rfc822_skipws (s)
  1079.     char **s;
  1080. {
  1081.   char tmp[MAILTMPLEN];
  1082.   char *t;
  1083.   long n = 0;
  1084.                 /* while whitespace or start of comment */
  1085.   while ((**s == ' ') || (n = (**s == '('))) {
  1086.     t = *s;            /* note comment pointer */
  1087.     if (**s == '(') while (n) {    /* while comment in effect */
  1088.       switch (*++*s) {        /* get character of comment */
  1089.       case '(':            /* nested comment? */
  1090.     n++;            /* increment nest count */
  1091.     break;
  1092.       case ')':            /* end of comment? */
  1093.     n--;            /* decrement nest count */
  1094.     break;
  1095.       case '"':            /* quoted string? */
  1096.     while (*++*s != '\"') if (!**s || (**s == '\\' && !*++*s)) {
  1097.       sprintf (tmp,"Unterminated quoted string within comment: %.80s",t);
  1098.       mm_log (tmp,PARSE);
  1099.       *t = '\0';        /* nuke duplicate messages in case reparse */
  1100.       return;
  1101.     }
  1102.     break;
  1103.       case '\\':        /* quote next character? */
  1104.     if (*++*s != '\0') break;
  1105.       case '\0':        /* end of string */
  1106.     sprintf (tmp,"Unterminated comment: %.80s",t);
  1107.     mm_log (tmp,PARSE);
  1108.     *t = '\0';        /* nuke duplicate messages in case reparse */
  1109.     return;            /* this is wierd if it happens */
  1110.       default:            /* random character */
  1111.     break;
  1112.       }
  1113.     }
  1114.     ++*s;            /* skip past whitespace character */
  1115.   }
  1116. }
  1117.  
  1118. /* Body contents utility and encoding/decoding routines */
  1119.  
  1120.  
  1121. /* Return body contents in normal form
  1122.  * Accepts: pointer to destination
  1123.  *        pointer to length of destination
  1124.  *        returned destination length
  1125.  *        source
  1126.  *        length of source
  1127.  *        source encoding
  1128.  * Returns: destination
  1129.  *
  1130.  * Originally, this routine was supposed to do decoding as well, but that was
  1131.  * moved to a higher level.  Now, it's merely a jacket into strcrlfcpy that
  1132.  * avoids the work for BASE64 and BINARY segments.
  1133.  */
  1134.  
  1135. char *rfc822_contents (dst,dstl,len,src,srcl,encoding)
  1136.     char **dst;
  1137.     unsigned long *dstl;
  1138.     unsigned long *len;
  1139.                     char *src;
  1140.     unsigned long srcl;
  1141.     unsigned short encoding;
  1142. {
  1143.   *len = 0;            /* in case we return an error */
  1144.   switch (encoding) {        /* act based on encoding */
  1145.   case ENCBASE64:        /* 3 to 4 binary */
  1146.   case ENCBINARY:        /* unmodified binary */
  1147.     if ((*len = srcl) > *dstl) {/* resize if not enough space */
  1148.       fs_give ((void **) dst);    /* fs_resize does an unnecessary copy */
  1149.       *dst = (char *) fs_get ((*dstl = srcl) + 1);
  1150.     }
  1151.     memcpy (*dst,src,srcl);    /* copy that many bytes */
  1152.     *(*dst + srcl) = '\0';    /* tie off destination */
  1153.     break;
  1154.   default:            /* all other cases return strcrlfcpy version */
  1155.     *len = strlen (*dst = strcrlfcpy (dst,dstl,src,srcl));
  1156.     break;
  1157.   }
  1158.   return *dst;            /* return the string */
  1159. }
  1160.  
  1161.  
  1162. /* Output RFC 822 message
  1163.  * Accepts: temporary buffer
  1164.  *        envelope
  1165.  *        body
  1166.  *        I/O routine
  1167.  *        stream for I/O routine
  1168.  * Returns: T if successful, NIL if failure
  1169.  */
  1170.  
  1171. long rfc822_output (t,env,body,f,s)
  1172.     char *t;
  1173.     ENVELOPE *env;
  1174.     BODY *body;
  1175.     soutr_t f;
  1176.     TCPSTREAM *s;
  1177. {
  1178.   rfc822_encode_body (env,body);/* encode body as necessary */
  1179.   rfc822_header (t,env,body);    /* build RFC822 header */
  1180.                 /* output header and body */
  1181.   return (*f) (s,t) && (body ? rfc822_output_body (body,f,s) : T);
  1182. }
  1183.  
  1184. /* Encode a body for 7BIT transmittal
  1185.  * Accepts: envelope
  1186.  *        body
  1187.  */
  1188.  
  1189. void rfc822_encode_body (env,body)
  1190.     ENVELOPE *env;
  1191.     BODY *body;
  1192. {
  1193.   void *f;
  1194.   PART *part;
  1195.   if (body) switch (body->type) {
  1196.   case TYPEMULTIPART:        /* multi-part */
  1197.     if (!body->parameter) {    /* cookie not set up yet? */
  1198.       char tmp[MAILTMPLEN];    /* make cookie not in BASE64 or QUOTEPRINT*/
  1199.       sprintf (tmp,"%ld-%ld-%ld:#%ld",gethostid (),random (),time (0),
  1200.            getpid ());
  1201.       body->parameter = mail_newbody_parameter ();
  1202.       body->parameter->attribute = cpystr ("BOUNDARY");
  1203.       body->parameter->value = cpystr (tmp);
  1204.     }
  1205.     part = body->contents.part;    /* encode body parts */
  1206.     do rfc822_encode_body (env,&part->body);
  1207.     while (part = part->next);    /* until done */
  1208.     break;
  1209. /* case MESSAGE:    */    /* here for documentation */
  1210.     /* Encapsulated messages are always treated as text objects at this point.
  1211.        This means that you must replace body->contents.msg with
  1212.        body->contents.text, which probably involves copying
  1213.        body->contents.msg.text to body->contents.text */
  1214.   default:            /* all else has some encoding */
  1215.     switch (body->encoding) {
  1216.     case ENC8BIT:        /* encode 8BIT into QUOTED-PRINTABLE */
  1217.                 /* remember old 8-bit contents */
  1218.       f = (void *) body->contents.text;
  1219.       body->contents.text = rfc822_8bit (body->contents.text,body->size.bytes,
  1220.                      &body->size.bytes);
  1221.       body->encoding = ENCQUOTEDPRINTABLE;
  1222.       fs_give (&f);        /* flush old binary contents */
  1223.       break;
  1224.     case ENCBINARY:        /* encode binary into BASE64 */
  1225.       f = body->contents.binary;/* remember old binary contents */
  1226.       body->contents.text = rfc822_binary (body->contents.binary,
  1227.                        body->size.bytes,&body->size.bytes);
  1228.       body->encoding = ENCBASE64;
  1229.       fs_give (&f);        /* flush old binary contents */
  1230.     default:            /* otherwise OK */
  1231.       break;
  1232.     }
  1233.     break;
  1234.   }
  1235. }
  1236.  
  1237. /* Output RFC 822 body
  1238.  * Accepts: body
  1239.  *        I/O routine
  1240.  *        stream for I/O routine
  1241.  * Returns: T if successful, NIL if failure
  1242.  */
  1243.  
  1244. long rfc822_output_body (body,f,s)
  1245.     BODY *body;
  1246.     soutr_t f;
  1247.     TCPSTREAM *s;
  1248. {
  1249.   PART *part;
  1250.   PARAMETER *param;
  1251.   char *cookie = NIL;
  1252.   char tmp[MAILTMPLEN];
  1253.   char *t;
  1254.                 /* multipart gets special handling */
  1255.   if (body->type == TYPEMULTIPART) {
  1256.     part = body->contents.part;    /* first body part */
  1257.                 /* find cookie */
  1258.     for (param = body->parameter; param && !cookie; param = param->next)
  1259.       if (!strcmp (param->attribute,"BOUNDARY")) cookie = param->value;
  1260.     if (!cookie) cookie = "-";    /* yucky default */
  1261.     do {            /* for each part */
  1262.       sprintf (t = tmp,"--%s\015\012",cookie);
  1263.       rfc822_write_body_header (&t,&part->body);
  1264.       (*f) (s,tmp);        /* output cookie and mini-header */
  1265.                 /* output contents */
  1266.       rfc822_output_body (&part->body,f,s);
  1267.     } while (part = part->next);/* until done */
  1268.     sprintf (tmp,"--%s--\015\012",cookie);
  1269.     (*f) (s,tmp);        /* output trailing cookie */
  1270.     return T;
  1271.   } else if (body->type == TYPEMESSAGE) {
  1272.     return (body->contents.msg.text && *body->contents.msg.text) ?
  1273.       (*f) (s,(char *) body->contents.msg.text) && (*f) (s,"\015\012") : T;
  1274. /*      return T; */
  1275.   } 
  1276.                 /* all else is text now */
  1277.   return (body->contents.text && *body->contents.text) ?
  1278.     (*f) (s,(char *) body->contents.text) && (*f) (s,"\015\012") : T;
  1279. }
  1280.  
  1281. /* Convert BASE64 contents to binary
  1282.  * Accepts: source
  1283.  *        length of source
  1284.  *        pointer to return destination length
  1285.  * Returns: destination as binary
  1286.  */
  1287.  
  1288. void *rfc822_base64 (src,srcl,len)
  1289.     unsigned char *src;
  1290.     unsigned long srcl;
  1291.     unsigned long *len;
  1292. {
  1293.   char c;
  1294.   void *ret = fs_get (4 + ((srcl * 3) / 4));
  1295.   char *d = (char *) ret;
  1296.   short e = 0;
  1297.   *len = 0;            /* in case we return an error */
  1298.   while (srcl--) {        /* until run out of characters */
  1299.     c = *src++;            /* simple-minded decode */
  1300.     if (isupper (c)) c -= 'A';
  1301.     else if (islower (c)) c -= 'a' - 26;
  1302.     else if (isdigit (c)) c -= '0' - 52;
  1303.     else if (c == '+') c = 62;
  1304.     else if (c == '/') c = 63;
  1305.     else if (c == '=') {    /* padding */
  1306.       switch (e++) {        /* check quantum position */
  1307.       case 2:
  1308.     if (*src != '=') return NIL;
  1309.     break;
  1310.       case 3:
  1311.     e = 0;            /* restart quantum */
  1312.     break;
  1313.       default:            /* impossible quantum position */
  1314.     fs_give (&ret);
  1315.     return NIL;
  1316.       }
  1317.       continue;
  1318.     }
  1319.     else continue;        /* junk character */
  1320.     switch (e++) {        /* install based on quantum position */
  1321.     case 0:
  1322.       *d = c << 2;        /* byte 1: high 6 bits */
  1323.       break;
  1324.     case 1:
  1325.       *d++ |= c >> 4;        /* byte 1: low 2 bits */
  1326.       *d = c << 4;        /* byte 2: high 4 bits */
  1327.       break;
  1328.     case 2:
  1329.       *d++ |= c >> 2;        /* byte 2: low 4 bits */
  1330.       *d = c << 6;        /* byte 3: high 2 bits */
  1331.       break;
  1332.     case 3:
  1333.       *d++ |= c;        /* byte 3: low 6 bits */
  1334.       e = 0;            /* reinitialize mechanism */
  1335.       break;
  1336.     }
  1337.   }
  1338.   *len = d - (char *) ret;    /* calculate data length */
  1339.   return ret;            /* return the string */
  1340. }
  1341.  
  1342. /* Convert binary contents to BASE64
  1343.  * Accepts: source
  1344.  *        length of source
  1345.  *        pointer to return destination length
  1346.  * Returns: destination as BASE64
  1347.  */
  1348.  
  1349. unsigned char *rfc822_binary (src,srcl,len)
  1350.     void *src;
  1351.     unsigned long srcl;
  1352.     unsigned long *len;
  1353. {
  1354.   unsigned char c,*ret,*d;
  1355.   unsigned char *s = (unsigned char *) src;
  1356.   char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  1357.   unsigned long i = ((srcl + 2) / 3) * 4;
  1358.   *len = i += 2 * ((i / 60) + 1);
  1359.   d = ret = (unsigned char *) fs_get (++i);
  1360.   for (i = 0; srcl; s += 3) {    /* process tuplets */
  1361.     *d++ = v[s[0] >> 2];    /* byte 1: high 6 bits (1) */
  1362.                 /* byte 2: low 2 bits (1), high 4 bits (2) */
  1363.     *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
  1364.                 /* byte 3: low 4 bits (2), high 2 bits (3) */
  1365.     *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
  1366.                 /* byte 4: low 6 bits (3) */
  1367.     *d++ = srcl ? v[s[2] & 0x3f] : '=';
  1368.     if (srcl) srcl--;        /* count third character if processed */
  1369.     if ((++i) == 15) {        /* output 60 characters? */
  1370.       i = 0;            /* restart line break count, insert CRLF */
  1371.       *d++ = '\015'; *d++ = '\012';
  1372.     }
  1373.   }
  1374.   *d++ = '\015'; *d++ = '\012';    /* insert final CRLF */
  1375.   *d = '\0';            /* tie off string */
  1376.   if ((d - ret) != *len) fatal ("rfc822_binary logic flaw");
  1377.   return ret;            /* return the resulting string */
  1378. }
  1379.  
  1380.  
  1381. /* Convert QUOTED-PRINTABLE contents to 8BIT
  1382.  * Accepts: source
  1383.  *        length of source
  1384.  *         pointer to return destination length
  1385.  * Returns: destination as 8-bit text
  1386.  */
  1387.  
  1388. unsigned char *rfc822_qprint (src, srcl, len)
  1389.      unsigned char *src;
  1390.      unsigned long srcl;
  1391.      unsigned long *len;
  1392. {
  1393.   unsigned char *ret = (unsigned char *) fs_get (srcl);
  1394.   unsigned char *d = ret;
  1395.   unsigned char *s = d;
  1396.   unsigned char c,e;
  1397.   *len = 0;            /* in case we return an error */
  1398.   while (c = *src++) {        /* until run out of characters */
  1399.     switch (c) {        /* what type of character is it? */
  1400.     case '=':            /* quoting character */
  1401.       switch (c = *src++) {    /* what does it quote? */
  1402.       case '\015':        /* non-significant line break */
  1403.     if (*src == '\012') src++;
  1404.     break;
  1405.       default:            /* two hex digits then */
  1406.     if (!isxdigit (c)) {    /* must be hex! */
  1407.       fs_give ((void **) &ret);
  1408.       return NIL;
  1409.     }
  1410.     if (isdigit (c)) e = c - '0';
  1411.     else e = c - (isupper (c) ? 'A' - 10 : 'a' - 10);
  1412.     c = *src++;        /* snarf next character */
  1413.     if (!isxdigit (c)) {    /* must be hex! */
  1414.       fs_give ((void **) &ret);
  1415.       return NIL;
  1416.     }
  1417.     if (isdigit (c)) c -= '0';
  1418.     else c -= (isupper (c) ? 'A' - 10 : 'a' - 10);
  1419.     *d++ = c + (e << 4);    /* merge the two hex digits */
  1420.     s = d;            /* note point of non-space */
  1421.     break;
  1422.       }
  1423.       break;
  1424.     case ' ':            /* space, possibly bogus */
  1425.       *d++ = c;            /* stash the space but don't update s */
  1426.       break;
  1427.     case '\015':        /* end of line */
  1428.       d = s;            /* slide back to last non-space, drop in */
  1429.     default:
  1430.       *d++ = c;            /* stash the character */
  1431.       s = d;            /* note point of non-space */
  1432.     }      
  1433.   }
  1434.   *d = '\0';            /* tie off results */
  1435.   *len = d - ret;        /* calculate length */
  1436.   return ret;            /* return the string */
  1437. }
  1438.  
  1439. /* Convert 8BIT contents to QUOTED-PRINTABLE
  1440.  * Accepts: source
  1441.  *        length of source
  1442.  *         pointer to return destination length
  1443.  * Returns: destination as quoted-printable text
  1444.  */
  1445.  
  1446. #define MAXL 75            /* 76th position only used by continuation = */
  1447.  
  1448. unsigned char *rfc822_8bit (src, srcl, len)
  1449.      unsigned char *src;
  1450.      unsigned long srcl;
  1451.      unsigned long *len;
  1452. {
  1453.   unsigned long lp = 0;
  1454.   unsigned char *ret = (unsigned char *) fs_get (3*srcl + srcl/MAXL + 2);
  1455.   unsigned char *d = ret;
  1456.   char *hex = "0123456789ABCDEF";
  1457.   unsigned char c;
  1458.   while (srcl--) {        /* for each character */
  1459.                 /* true line break? */
  1460.     if (((c = *src++) == '\015') && (*src == '\012') && srcl) {
  1461.       *d++ = '\015'; *d++ = *src++; srcl--;
  1462.       lp = 0;            /* reset line count */
  1463.     }
  1464.     else {            /* not a line break */
  1465.                 /* quoting required? */
  1466.       if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
  1467.       ((c == ' ') && (*src == '\015'))) {
  1468.     if ((lp += 3) > MAXL) {    /* yes, would line overflow? */
  1469.       *d++ = '='; *d++ = '\015'; *d++ = '\012';
  1470.       lp = 3;        /* set line count */
  1471.     }
  1472.     *d++ = '=';        /* quote character */
  1473.     *d++ = hex[c >> 4];    /* high order 4 bits */
  1474.     *d++ = hex[c & 0xf];    /* low order 4 bits */
  1475.       }
  1476.       else {            /* ordinary character */
  1477.     if ((++lp) > MAXL) {    /* would line overflow? */
  1478.       *d++ = '='; *d++ = '\015'; *d++ = '\012';
  1479.       lp = 1;        /* set line count */
  1480.     }
  1481.     *d++ = c;        /* ordinary character */
  1482.       }
  1483.     }
  1484.   }
  1485.   *d = '\0';            /* tie off destination */
  1486.   *len = d - ret;        /* calculate true size */
  1487.                 /* try to give some space back */
  1488.   fs_resize ((void **) &ret,*len);
  1489.   return ret;
  1490. }
  1491.  
  1492. /*
  1493.    On the decoding side, we take advantage of the distinction between
  1494.    a CRLF (turned into a LF for UNIX when it get here) and an =0D=0A.
  1495.    Something CRLF on the wire is clearly meant to be in the local
  1496.    new line convention. Something =OD=OA is clearly meant to stay
  1497.    as CRLF
  1498.  
  1499.  */
  1500.  
  1501. /* Convert QUOTED-PRINTABLE contents to 8BIT
  1502.  * Accepts: source
  1503.  *        length of source
  1504.  *         pointer to return destination length
  1505.  * Returns: destination as 8-bit text
  1506.  */
  1507.  
  1508. unsigned char *rfc822_qprint_nl (src,  srcl, len)
  1509.      unsigned char *src;
  1510.      unsigned long srcl;
  1511.      unsigned long *len;
  1512. {
  1513.   unsigned char *ret = (unsigned char *) fs_get (srcl);
  1514.   unsigned char *d = ret;
  1515.   unsigned char *s = d;
  1516.   unsigned char c,e;
  1517.   *len = 0;            /* in case we return an error */
  1518.   while (c = *src++) {        /* until run out of characters */
  1519.     switch (c) {        /* what type of character is it? */
  1520.     case '=':            /* quoting character */
  1521.       switch (c = *src++) {    /* what does it quote? */
  1522.       case '\012':        /* non-significant line break */
  1523.     break;
  1524.       default:            /* two hex digits then */
  1525.     if (!isxdigit (c)) {    /* must be hex! */
  1526.       fs_give ((void **) &ret);
  1527.       return NIL;
  1528.     }
  1529.     if (isdigit (c)) e = c - '0';
  1530.     else e = c - (isupper (c) ? 'A' - 10 : 'a' - 10);
  1531.     c = *src++;        /* snarf next character */
  1532.     if (!isxdigit (c)) {    /* must be hex! */
  1533.       fs_give ((void **) &ret);
  1534.       return NIL;
  1535.     }
  1536.     if (isdigit (c)) c -= '0';
  1537.     else c -= (isupper (c) ? 'A' - 10 : 'a' - 10);
  1538.     *d++ = c + (e << 4);    /* merge the two hex digits */
  1539.     s = d;            /* note point of non-space */
  1540.     break;
  1541.       }
  1542.       break;
  1543.     case ' ':            /* space, possibly bogus */
  1544.       *d++ = c;            /* stash the space but don't update s */
  1545.       break;
  1546.     case '\012':        /* end of line */
  1547.       d = s;            /* slide back to last non-space, drop in */
  1548.     default:
  1549.       *d++ = c;            /* stash the character */
  1550.       s = d;            /* note point of non-space */
  1551.     }      
  1552.   }
  1553.   *d = '\0';            /* tie off results */
  1554.   *len = d - ret;        /* calculate length */
  1555.   return ret;            /* return the string */
  1556. }
  1557.